using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Linq.Dynamic.Core;
using Abp.Application.Services.Dto;
using Abp.Auditing;
using Abp.Authorization;
using Abp.Configuration.Startup;
using Abp.Domain.Repositories;
using Abp.EntityHistory;
using Abp.Extensions;
using Abp.Linq.Extensions;
using L._52ABP.Application.Dtos;
using Microsoft.EntityFrameworkCore;
using Yozeev.Auditing.Dtos;
using Yozeev.Auditing.Dtos.EntityChange;
using Yozeev.Auditing.Exporting;
using Yozeev.Authorization;
using Yozeev.EntityHistory;
using Yozeev.NamespaceHelper;
using Yozeev.UserManagerment.Users;
using EntityHistoryHelper = Yozeev.EntityHistory.EntityHistoryHelper;
using Newtonsoft.Json;
using Yozeev.BusinessLogic;
using System;

namespace Yozeev.Auditing
{
    [DisableAuditing]
    [AbpAuthorize]
    public class AuditLogAppService : YozeevAppServiceBase, IAuditLogAppService
    {
        private readonly IRepository<AuditLog, long> _auditLogRepository;
        private readonly NamespaceHelperManager _namespaceHelperManager;
        private readonly IRepository<User, long> _userRepository;
        private readonly IAuditLogListExcelExporter _auditLogListExcelExporter;
        private readonly IAbpStartupConfiguration _abpStartupConfiguration;
        private readonly IRepository<EntityChange, long> _entityChangeRepository;

        private readonly IRepository<EntityChangeSet, long> _entityChangeSetRepository;

        private readonly IRepository<EntityPropertyChange, long> _entityPropertyChangeRepository;

        private readonly IRepository<Source, Guid> _sourceRepository;

        public AuditLogAppService(IRepository<AuditLog, long> auditLogRepository,
            IRepository<User, long> userRepository
            , NamespaceHelperManager namespaceHelperManager
            , IAuditLogListExcelExporter auditLogListExcelExporter
            , IAbpStartupConfiguration abpStartupConfiguration
            , IRepository<EntityChange, long> entityChangeRepository
            , IRepository<EntityChangeSet, long> entityChangeSetRepository
            , IRepository<EntityPropertyChange, long> entityPropertyChangeRepository
            , IRepository<Source, Guid> sourceRepository
            )
        {
            _auditLogRepository = auditLogRepository;
            _userRepository = userRepository;
            _namespaceHelperManager = namespaceHelperManager;
            _auditLogListExcelExporter = auditLogListExcelExporter;
            _abpStartupConfiguration = abpStartupConfiguration;
            _entityChangeRepository = entityChangeRepository;
            _entityChangeSetRepository = entityChangeSetRepository;
            _entityPropertyChangeRepository = entityPropertyChangeRepository;
            _sourceRepository = sourceRepository;
        }


        public async Task<PagedResultDto<AuditLogListDto>> GetPagedAuditLogs(GetAuditLogsInput input)
        {
            var query = CreateAuditLogAndUsersQuery(input);

            var resultCount = await query.CountAsync();
            var results = await query
                .AsNoTracking()
                .OrderBy(input.Sorting)// TODO: OrderBy(input.Sorting)
                .PageBy(input)
                .ToListAsync();

            var auditLogListDtos = ConvertToAuditLogListDtos(results);

            return new PagedResultDto<AuditLogListDto>(resultCount, auditLogListDtos);
        }

        public async Task<FileDto> GetAuditLogsToExcelAsync(GetAuditLogsInput input)
        {
            var auditLogs = await CreateAuditLogAndUsersQuery(input)
                .AsNoTracking()
                .OrderByDescending(al => al.AuditLogInfo.ExecutionTime)
                .ToListAsync();

            var auditLogListDtos = ConvertToAuditLogListDtos(auditLogs);

            return _auditLogListExcelExporter.ExportAuditLogToFile(auditLogListDtos);
        }

        #region EntityHistory 服务
        public List<NameValueDto> GetEntityHistoryObjectTypes()
        {
            var entityHistoryObjectTypes = new List<NameValueDto>();
            var enabledEntities = (_abpStartupConfiguration.GetCustomConfig()
                                      .FirstOrDefault(x => x.Key == EntityHistoryHelper.EntityHistoryConfigurationName)
                                      .Value as EntityHistoryUiSetting)?.EnabledEntities ?? new List<string>();

            if (AbpSession.TenantId == null)
            {
                enabledEntities = EntityHistoryHelper.HostSideTrackedTypes.Select(t => t.FullName).Intersect(enabledEntities).ToList();
            }
            else
            {
                enabledEntities = EntityHistoryHelper.TenantSideTrackedTypes.Select(t => t.FullName).Intersect(enabledEntities).ToList();
            }

            foreach (var enabledEntity in enabledEntities)
            {
                entityHistoryObjectTypes.Add(new NameValueDto(L(enabledEntity), enabledEntity));
            }

            return entityHistoryObjectTypes;
        }
        public async Task<PagedResultDto<EntityChangeListDto>> GetEntityChanges(GetEntityChangeInput input)
        {
            var query = CreateEntityChangesAndUsersQuery(input);

            var resultCount = await query.CountAsync();
            var results = await query
                .OrderBy(input.Sorting)
                .PageBy(input)
                .ToListAsync();

            var entityChangeListDtos = ConvertToEntityChangeListDtos(results);

            return new PagedResultDto<EntityChangeListDto>(resultCount, entityChangeListDtos);
        }

        public async Task<FileDto> GetEntityChangesToExcel(GetEntityChangeInput input)
        {
            var entityChanges = await CreateEntityChangesAndUsersQuery(input)
                .AsNoTracking()
                .OrderByDescending(ec => ec.EntityChange.EntityChangeSetId)
                .ThenByDescending(ec => ec.EntityChange.ChangeTime)
                .ToListAsync();

            var entityChangeListDtos = ConvertToEntityChangeListDtos(entityChanges);

            return _auditLogListExcelExporter.ExportEntityChangeToFile(entityChangeListDtos);
        }



        #region EntityChange私有服务方法

        private IQueryable<EntityChangeAndUser> CreateEntityChangesAndUsersQuery(GetEntityChangeInput input)
        {
            var query = from entityChangeSet in _entityChangeSetRepository.GetAll()
                join entityChange in _entityChangeRepository.GetAll() on entityChangeSet.Id equals entityChange.EntityChangeSetId
                join user in _userRepository.GetAll() on entityChangeSet.UserId equals user.Id
                where entityChange.ChangeTime >= input.StartDate && entityChange.ChangeTime <= input.EndDate
                select new EntityChangeAndUser
                {
                    EntityChange = entityChange,
                    User = user
                };

            query = query
                .WhereIf(!input.UserName.IsNullOrWhiteSpace(), item => item.User.UserName.Contains(input.UserName))
                .WhereIf(!input.EntityTypeFullName.IsNullOrWhiteSpace(), item => item.EntityChange.EntityTypeFullName.Contains(input.EntityTypeFullName));

            return query;
        }


        private List<EntityChangeListDto> ConvertToEntityChangeListDtos(List<EntityChangeAndUser> results)
        {
            return results.Select(
                result =>
                {
                    var entityChangeListDto = ObjectMapper.Map<EntityChangeListDto>(result.EntityChange);
                    entityChangeListDto.UserName = result.User?.UserName;
                    return entityChangeListDto;
                }).ToList();
        }

        #endregion


        #endregion





        #region 审计日志私有服务

        /// <summary>
        ///     创建审计日志用户的查询服务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private IQueryable<AuditLogAndUser> CreateAuditLogAndUsersQuery(GetAuditLogsInput input)
        {
            IQueryable<AuditLogAndUser> query;
            if (input.StartDate.HasValue && input.EndDate.HasValue)
                query = from auditLog in _auditLogRepository.GetAll()
                        join user in _userRepository.GetAll() on auditLog.UserId equals user.Id into userJoin
                        from joinedUser in userJoin.DefaultIfEmpty()
                        where auditLog.ExecutionTime >= input.StartDate && auditLog.ExecutionTime <= input.EndDate
                        select new AuditLogAndUser { AuditLogInfo = auditLog, UserInfo = joinedUser };
            else
                query = from auditLog in _auditLogRepository.GetAll()
                        join user in _userRepository.GetAll() on auditLog.UserId equals user.Id into userJoin
                        from joinedUser in userJoin.DefaultIfEmpty()
                        select new AuditLogAndUser { AuditLogInfo = auditLog, UserInfo = joinedUser };

            query = query
                .WhereIf(!input.UserName.IsNullOrWhiteSpace(), item => item.UserInfo.UserName.Contains(input.UserName))
                .WhereIf(!input.ServiceName.IsNullOrWhiteSpace(),
                    item => item.AuditLogInfo.ServiceName.Contains(input.ServiceName))
                .WhereIf(!input.MethodName.IsNullOrWhiteSpace(),
                    item => item.AuditLogInfo.MethodName.Contains(input.MethodName))
                .WhereIf(!input.BrowserInfo.IsNullOrWhiteSpace(),
                    item => item.AuditLogInfo.BrowserInfo.Contains(input.BrowserInfo))
                .WhereIf(input.MinExecutionDuration.HasValue && input.MinExecutionDuration > 0,
                    item => item.AuditLogInfo.ExecutionDuration >= input.MinExecutionDuration.Value)
                .WhereIf(input.MaxExecutionDuration.HasValue && input.MaxExecutionDuration < int.MaxValue,
                    item => item.AuditLogInfo.ExecutionDuration <= input.MaxExecutionDuration.Value)
                .WhereIf(input.HasException == true,
                    item => item.AuditLogInfo.Exception != null && item.AuditLogInfo.Exception != "")
                .WhereIf(input.HasException == false,
                    item => item.AuditLogInfo.Exception == null || item.AuditLogInfo.Exception == "");
            return query;
        }

        private List<AuditLogListDto> ConvertToAuditLogListDtos(List<AuditLogAndUser> results)
        {
            return results.Select(
                result =>
                {
                    var auditLogListDto = ObjectMapper.Map<AuditLogListDto>(result.AuditLogInfo);
                    auditLogListDto.UserName = result.UserInfo == null ? null : result.UserInfo.UserName;
                    auditLogListDto.ServiceName = _namespaceHelperManager.SplitNameSpace(auditLogListDto.ServiceName);
                    return auditLogListDto;
                }).ToList();
        }



        #endregion
        #region 增加
        /// <summary>
        /// 登录日志查询
        /// </summary>
        /// <param name="input">查询参数，排序方式</param>
        /// <returns></returns>
        public async Task<PagedResultDto<LoginLogModel>> GetPagedLoginLogs(GetLoginLogsInput input)
        {
            //查询出所有时间范围内的登录日志
            var query = from auditLog in _auditLogRepository.GetAll().Where(audit => audit.MethodName == "Authenticate")
                        .WhereIf(input.StartDate.HasValue, audit => audit.ExecutionTime >= input.StartDate)
                        .WhereIf(input.EndDate.HasValue, audit => audit.ExecutionTime <= input.EndDate)
                        select new
                        {
                            auditLog,
                            authenticateModel = JsonConvert.DeserializeObject<AllModel>(auditLog.Parameters).model,
                        };
            //用户名
            var userList = from q in query
                           from user in _userRepository.GetAll()
                           where user.UserName == q.authenticateModel.userNameOrEmailAddress || user.EmailAddress == q.authenticateModel.userNameOrEmailAddress
                           select new { 
                                q,
                                user,
                           };
            userList = userList.WhereIf(!string.IsNullOrWhiteSpace(input.UserName), u => input.UserName == u.user.UserName || u.user.EmailAddress == input.UserName);
            //登录端查询
            var list = from temp in userList
                       join soure in _sourceRepository.GetAll()
                       .WhereIf(!string.IsNullOrWhiteSpace(input.SourceCode), u => u.SourceCode == input.SourceCode)
                       .WhereIf(!string.IsNullOrWhiteSpace(input.SourceName), u => u.SourceName == input.SourceName)
                       on (temp.q.authenticateModel.sourceCode == null ? "Reception" : temp.q.authenticateModel.sourceCode) equals soure.SourceCode
                       select new LoginLogModel
                       {
                           UserNameOrEmailAddress = temp.q.authenticateModel.userNameOrEmailAddress,
                           SourceName = soure.SourceName,
                           SourceCode = temp.q.authenticateModel.sourceCode,
                           ClientIpAddress = temp.q.auditLog.ClientIpAddress,
                           ExecutionTime = temp.q.auditLog.ExecutionTime,
                           HasException = string.IsNullOrEmpty(temp.q.auditLog.Exception),
                           Exception = temp.q.auditLog.Exception,
                       };
            var count = list.Count();
            var loginLogList = list.AsQueryable().OrderBy(input.Sorting)
                    .PageBy(input)
                    .ToList();
            return new PagedResultDto<LoginLogModel>(count, loginLogList);
        }
        #endregion
    }
}